
#include <BRender>
#include <BOpenGL>

#include <BTerrain>
#include "member_BTerrain.h"

using namespace BWE;

#define member					(*(member_BTerrain*)_ptr)
#define member_allocate()		_ptr = new member_BTerrain(this)
#define member_release()		delete (member_BTerrain*)_ptr

BTerrain::BTerrain()
{
	member_allocate();
}
BTerrain::BTerrain(const BString& name)
{
	member_allocate();
	this->setName(name);
}
BTerrain::BTerrain(BReal width, BReal height, int column, int row)
{
	member_allocate();
	this->setWidth(width);
	this->setHeight(height);
	member.elevation.setColumn(column);
	member.elevation.setRow(row);
	this->setElevation(member.elevation);
	this->flush();
}
BTerrain::BTerrain(const BTerrain& other)
{
	member_allocate();
	this->copy(other);
}
BTerrain::~BTerrain()
{
	member_release();
}

void BTerrain::copy(const BShape& other)
{
	BShape::copy(other);
}
BShape* BTerrain::clone() const
{
	return new BTerrain(*this);
}

void BTerrain::setOptimized(bool optimized)
{
	if (optimized && !member.quadTree)
	{
		member.quadTree = new QuadTree();
		member.slotFreshQuadTree();
		member.connect(this, Signal_SpaceChanged, &member_BTerrain::slotFreshQuadTree);
		return;
	}
	if (!optimized && member.quadTree)
	{
		member.quadTree = 0;
		member.disconnect(this);
		return;
	}
}
bool BTerrain::optimized() const
{
	return member.quadTree.valid();
}

void BTerrain::setAlign(Align align)
{
	if (member.align != align)
	{
		member.align = align;
		member.dirtyArea = true;
		this->dirty();
	}
}
Align BTerrain::align() const
{
	return member.align;
}

void BTerrain::setWidth(BReal width)
{
	if (member.width != width)
	{
		member.width = width;
		member.dirtyArea = true;
		this->dirty();
	}
}
BReal BTerrain::width() const
{
	return member.width;
}

void BTerrain::setHeight(BReal height)
{
	if (member.height != height)
	{
		member.height = height;
		member.dirtyArea = true;
		this->dirty();
	}
}
BReal BTerrain::height() const
{
	return member.height;
}

void BTerrain::setSkirtHeight(BReal skirtHeight)
{
	if (member.skirtHeight != skirtHeight)
	{
		member.skirtHeight = skirtHeight;
		member.dirtySkirt = true;
		this->dirty();
	}
}
BReal BTerrain::skirtHeight() const
{
	return member.skirtHeight;
}

void BTerrain::setElevation(const BRealTable& elevation)
{
	member.elevation = elevation;
	BVectorArray* vertices = member.vertices;
	if (!vertices || vertices->size() != member.elevation.size())
	{
		member.dirtyArea = true;
	}
	else
	{
		int size = member.elevation.size();
		BReal* zs = member.elevation.data();
		for (int i = 0; i < size; i++)
		{
			(*vertices)[i].z() = zs[i];
		}
		this->smooth();
	}
	this->dirty();
}
const BRealTable& BTerrain::elevation() const
{
	return member.elevation;
}
BRealTable& BTerrain::elevation()
{
	return member.elevation;
}

void BTerrain::setVertices(BVectorArray* vertices)
{
	if (member.vertices && member.vertices->size() != vertices->size())
		return;
	member.vertices = vertices;
	this->dirty();
}
const BVectorArray* BTerrain::vertices() const
{
	return member.vertices;
}
BVectorArray* BTerrain::vertices()
{
	return member.vertices;
}

void BTerrain::setNormals(BVectorArray* normals)
{
	if (member.normals && member.normals->size() != normals->size())
		return;
	member.normals = normals;
	this->fresh();
}
const BVectorArray* BTerrain::normals() const
{
	return member.normals;
}
BVectorArray* BTerrain::normals()
{
	return member.normals;
}

void BTerrain::setColors(BColorArray* colors)
{
	if (member.colors && member.colors->size() != colors->size())
		return;
	member.colors = colors;
	this->fresh();
}
const BColorArray* BTerrain::colors() const
{
	return member.colors;
}
BColorArray* BTerrain::colors()
{
	return member.colors;
}

void BTerrain::setTexCoords(BCoordArray* texCoords, int unit)
{
	if (member.vertices && member.vertices->size() != texCoords->size())
		return;
	if (unit < 0 || unit >= 8)
		return;
	member.coords = texCoords;
	if(this->texture(0))
		this->fresh();
}
const BCoordArray* BTerrain::texCoords(int unit) const
{
	return member.coords;
}
BCoordArray* BTerrain::texCoords(int unit)
{
	return member.coords;
}

void BTerrain::setIndices(BIndexArray* indices)
{
	member.indices = indices;
	this->fresh();
}
const BIndexArray* BTerrain::indices() const
{
	return member.indices;
}
BIndexArray* BTerrain::indices()
{
	return member.indices;
}

void BTerrain::clear()
{
	member.vertices = 0;
	member.normals = 0;
	member.colors = 0;
	member.indices = 0;
	member.coords = 0;
	this->dirty();
}

bool BTerrain::empty() const
{
	return member.elevation.empty();
}
void BTerrain::smooth()
{
	BVectorArray* vertices = member.vertices;
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != vertices->size())
	{
		normals = new BVectorArray(vertices->size());
		member.normals = normals;
	}
	normals->fill(BVector());
	BIndexArray* indices = member.indices;
	if (indices)
	{
		int size = indices->size();
		for (int i = 0; i < size; i += 4)
		{
			unsigned int ia = (*indices)[i + 0];
			unsigned int ib = (*indices)[i + 1];
			unsigned int ic = (*indices)[i + 2];
			unsigned int id = (*indices)[i + 3];
			const BVector& va = (*vertices)[ia];
			const BVector& vb = (*vertices)[ib];
			const BVector& vc = (*vertices)[ic];
			BVector normal = (vb - va).cross(vc - vb);
			normal.normalize();
			(*normals)[ia] += normal;
			(*normals)[ib] += normal;
			(*normals)[ic] += normal;
			(*normals)[id] += normal;
		}
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i].normalize();
	}
	emit(Signal_Fresh);
}
void BTerrain::reverse()
{
	BIndexArray* indices = member.indices;
	for (int i = 0; i < indices->size(); i += 4)
	{
		unsigned int& ib = (*indices)[i + 1];
		unsigned int& id = (*indices)[i + 3];
		bSwap(ib, id);
	}
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != this->vertices()->size())
	{
		smooth();
		return;
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i] = -(*normals)[i];
	}
}

bool BTerrain::query(const BString& text, BValue& value) const
{
	if (text == "quadtree")
	{
		value = member.quadTree;
		return true;
	}
	return BShape::query(text, value);
}

void BTerrain::refresh(BBox& box)
{
	member.localSpace.reset();
	if (member.align & Align_Top)
		member.localSpace.min().y() = -member.height;
	if (member.align & Align_Right)
		member.localSpace.min().x() = -member.width;
	if (member.align & Align_HCenter)
		member.localSpace.min().x() = -member.width / 2;
	if (member.align & Align_VCenter)
		member.localSpace.min().y() = -member.height / 2;
	member.localSpace.max().x() = member.localSpace.min().x() + member.width;
	member.localSpace.max().y() = member.localSpace.min().y() + member.height;
	const BRealTable& elevation = member.elevation;
	BReal minz = elevation.first();
	BReal maxz = elevation.first();
	for (auto it = elevation.begin(); it.valid(); ++it)
	{
		BReal z = *it;
		if (minz > z)
			minz = z;
		if (maxz < z)
			maxz = z;
	}
	member.localSpace.min().z() = minz;
	member.localSpace.max().z() = maxz;
	if (member.dirtyArea)
	{
		member.rebuild();
		member.dirtyArea = false;
	}
	box = member.localSpace;
}
void BTerrain::render(BRender& render)
{
	if (!member.vertices || member.vertices->empty() || member.indices.empty())
		return;
	if (member.vertices)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		BOpenGL::glVertexArray(member.vertices);
	}
	if (member.normals)
	{
		glEnableClientState(GL_NORMAL_ARRAY);
		BOpenGL::glNormalArray(member.normals);
	}
	if (member.colors)
	{
		glEnableClientState(GL_COLOR_ARRAY);
		glColorPointer(4, GL_UNSIGNED_BYTE, 0, member.colors->data());
	}
	if (member.coords && this->texture(0))
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, member.coords->data());
	}
	if (member.indices)
	{
		glEnableClientState(GL_INDEX_ARRAY);
		glDrawElements(GL_QUADS, member.indices->size(), GL_UNSIGNED_INT, member.indices->data());
		glDisableClientState(GL_INDEX_ARRAY);
	}
	if (member.vertices)
	{
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	if (member.normals)
	{
		glDisableClientState(GL_NORMAL_ARRAY);
	}
	if (member.colors)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	if (member.coords && this->texture(0))
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
}
